#include "Stdafx.h"
#include "Direct3DDevice9.h"

#include "Direct3DSurface9.h"
#include "Direct3DSwapChain9.h"
#include "Direct3DTexture9.h"
#include "Direct3DVertexBuffer9.h"
#include "Direct3DIndexBuffer9.h"
#include "Direct3DPixelShader9.h"
#include "Direct3DVertexShader9.h"
#include "Direct3DVertexDeclaration9.h"
#include "D3DXBuffer.h"

using namespace Vortex::Wrapper::Direct3D;

Direct3DDevice9::Direct3DDevice9(IDirect3DDevice9* device) : _handle(device) {
}

Direct3DDevice9::~Direct3DDevice9() {
	_handle->Release();
}

UINT Direct3DDevice9::GetAvailableTextureMem() {
	return _handle->GetAvailableTextureMem();
}

UINT Direct3DDevice9::GetNumberOfSwapChains() {
	return _handle->GetNumberOfSwapChains();
}

HResult Direct3DDevice9::GetSwapChain(UINT iSwapChain, [Out] Direct3DSwapChain9^% result) {
	result = nullptr;
	IDirect3DSwapChain9* pHandle = NULL;

	HResult hr = HResult(_handle->GetSwapChain(iSwapChain, &pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DSwapChain9(pHandle);
	}

	return hr;
}

HResult Direct3DDevice9::GetDepthStencilSurface([Out] Direct3DSurface9^% result) {
	result = nullptr;
	IDirect3DSurface9* pHandle = NULL;

	HResult hr = HResult(_handle->GetDepthStencilSurface(&pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DSurface9(pHandle);
	}

	return hr;
}

HResult Direct3DDevice9::GetBackBuffer(UINT iSwapChain, UINT BackBuffer, [Out] Direct3DSurface9^% result) {
	result = nullptr;
	IDirect3DSurface9* pHandle = NULL;

	HResult hr = HResult(_handle->GetBackBuffer(iSwapChain, BackBuffer, D3DBACKBUFFER_TYPE_MONO, &pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DSurface9(pHandle);
	}

	return hr;
}

HResult Direct3DDevice9::BeginScene() {
	return HResult(_handle->BeginScene());
}

HResult Direct3DDevice9::EndScene() {
	return HResult(_handle->EndScene());
}

HResult Direct3DDevice9::Clear(Nullable<System::Drawing::Rectangle> rectangle, D3DClearFlags Flags, D3DCOLOR Color, float Z, DWORD Stencil) {
	D3DRECT rect, *pRect = NULL;
	DWORD count = 0;
	if (rectangle.HasValue) {
		rect.x1 = rectangle.Value.X;
		rect.y1 = rectangle.Value.Y;
		rect.x2 = rectangle.Value.Right;
		rect.y2 = rectangle.Value.Bottom;
		count = 1;
		pRect = &rect;
	}
	return HResult(_handle->Clear(count, pRect, (DWORD)Flags, Color, Z, Stencil));
}

///<summary>Creates additional swap chain for rendering</summary>
HResult Direct3DDevice9::CreateAdditionalSwapChain(PresentParameters% presentParams, Direct3DSwapChain9^% swapChain) {
	D3DPRESENT_PARAMETERS pp = presentParams.ToD3DPRESENT_PARAMETERS();

	swapChain = nullptr;
	IDirect3DSwapChain9* pHandle = NULL;
	
	HResult hr = HResult(_handle->CreateAdditionalSwapChain(&pp, &pHandle));
	if (hr.IsSuccess) {
		swapChain = gcnew Direct3DSwapChain9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateDepthStencilSurface(UINT Width, UINT Height, D3DFormat format, D3DMultisampleType MultiSample, 
								  DWORD MultisampleQuality, bool Discard, [Out] Direct3DSurface9^% surface) {
	surface = nullptr;
	IDirect3DSurface9* pHandle = NULL;
	
	HResult hr = HResult(_handle->CreateDepthStencilSurface(Width, Height, (D3DFORMAT)format, (D3DMULTISAMPLE_TYPE)MultiSample, MultisampleQuality, Discard, &pHandle, NULL));
	if (hr.IsSuccess) {
		surface = gcnew Direct3DSurface9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateIndexBuffer(UINT length, D3DUsage usage, D3DFormat format, D3DPool pool, [Out] Direct3DIndexBuffer9^% indexBuffer) {
	indexBuffer = nullptr;
	IDirect3DIndexBuffer9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateIndexBuffer(length, (DWORD)usage, (D3DFORMAT)format, (D3DPOOL)pool, &pHandle, NULL));
	if (hr.IsSuccess) {
		indexBuffer = gcnew Direct3DIndexBuffer9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateVertexBuffer(UINT length, D3DUsage usage, D3DFVF fvf, D3DPool pool, [Out] Direct3DVertexBuffer9^% vertexBuffer) {
	vertexBuffer = nullptr;
	IDirect3DVertexBuffer9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateVertexBuffer(length, (DWORD)usage, (DWORD)fvf, (D3DPOOL)pool, &pHandle, NULL));
	if (hr.IsSuccess) {
		vertexBuffer = gcnew Direct3DVertexBuffer9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateRenderTarget(UINT width, UINT height, D3DFormat format, D3DMultisampleType multisample, DWORD multisampleQuality, BOOL lockable, [Out] Direct3DSurface9^% result) {
	result = nullptr;
	IDirect3DSurface9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateRenderTarget(width, height, (D3DFORMAT)format, (D3DMULTISAMPLE_TYPE)multisample, multisampleQuality, lockable, &pHandle, NULL));
	if (hr.IsSuccess) {
		result = gcnew Direct3DSurface9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateTexture(UINT width, UINT height, UINT levels, D3DUsage usage, D3DFormat format, D3DPool pool, [Out] Direct3DTexture9^% result) {
	result = nullptr;
	IDirect3DTexture9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateTexture(width, height, levels, (DWORD)usage, (D3DFORMAT)format, (D3DPOOL)pool, &pHandle, NULL));
	if (hr.IsSuccess) {
		result = gcnew Direct3DTexture9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateVertexShader(IntPtr pFunction, Direct3DVertexShader9^% result) {
	result = nullptr;
	IDirect3DVertexShader9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateVertexShader((DWORD*)pFunction.ToPointer(), &pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DVertexShader9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreatePixelShader(IntPtr pFunction, Direct3DPixelShader9^% result) {
	result = nullptr;
	IDirect3DPixelShader9* pHandle = NULL;

	HResult hr = HResult(_handle->CreatePixelShader((DWORD*)pFunction.ToPointer(), &pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DPixelShader9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateVertexDeclaration(array<D3DVertexElement>^ elements, [Out] Direct3DVertexDeclaration9^% result) {
	result = nullptr;
	IDirect3DVertexDeclaration9* pHandle = NULL;

	pin_ptr<D3DVertexElement> pData = &elements[0];
	D3DVERTEXELEMENT9* pElementData = (D3DVERTEXELEMENT9*)pData;

	HResult hr = HResult(_handle->CreateVertexDeclaration(pElementData, &pHandle));
	if (hr.IsSuccess) {
		result = gcnew Direct3DVertexDeclaration9(pHandle);
	}
	return hr;
}

HResult Direct3DDevice9::CreateOffscreenPlainSurface(UINT width, UINT height, D3DFormat format, D3DPool pool, [Out] Direct3DSurface9^% result) {
	result = nullptr;
	IDirect3DSurface9* pHandle = NULL;

	HResult hr = HResult(_handle->CreateOffscreenPlainSurface(width, height, (D3DFORMAT)format, (D3DPOOL)pool, &pHandle, NULL));
	if (hr.IsSuccess) {
		result = gcnew Direct3DSurface9(pHandle);
	}
	return hr;
}


///<summary>Set vertex declaration</summary>
HResult Direct3DDevice9::SetVertexDeclaration(Direct3DVertexDeclaration9^ vertexDeclaration) {
	return HResult(_handle->SetVertexDeclaration(vertexDeclaration->NativeHandle));
}

HResult Direct3DDevice9::SetFVF(D3DFVF fvf) {
	return HResult(_handle->SetFVF((DWORD)fvf));
}

HResult Direct3DDevice9::SetDepthStencilSurface(Direct3DSurface9^ surface) {
	return HResult(_handle->SetDepthStencilSurface(surface ? surface->NativeHandle : NULL));
}

HResult Direct3DDevice9::SetIndices(Direct3DIndexBuffer9^ indexBuffer) {
	return HResult(_handle->SetIndices(indexBuffer->NativeHandle));
}

HResult Direct3DDevice9::SetRenderTarget(DWORD renderTargetIndex, Direct3DSurface9^ renderTargetSurface) {
	return HResult(_handle->SetRenderTarget(renderTargetIndex, renderTargetSurface->NativeHandle));
}

HResult Direct3DDevice9::SetStreamSource(UINT streamNumber, Direct3DVertexBuffer9^ streamData, UINT offsetInBytes, UINT stride) {
	return HResult(_handle->SetStreamSource(streamNumber, streamData->NativeHandle, offsetInBytes, stride));
}

HResult Direct3DDevice9::SetTexture(DWORD sampler, Direct3DTexture9^ texture) {
	return HResult(_handle->SetTexture(sampler, texture ? texture->NativeHandle : NULL));
}

HResult Direct3DDevice9::SetScissorRect(System::Drawing::Rectangle rect) {
	RECT _Rect = {rect.Left, rect.Top, rect.Right, rect.Bottom };
	return HResult(_handle->SetScissorRect(&_Rect));
}

HResult Direct3DDevice9::SetRenderState(D3DRenderState state, DWORD value) {
	return HResult(_handle->SetRenderState((D3DRENDERSTATETYPE)state, value));
}

HResult Direct3DDevice9::SetRenderState(D3DRenderState state, bool value) {
	return HResult(_handle->SetRenderState((D3DRENDERSTATETYPE)state, value));
}

HResult Direct3DDevice9::SetSamplerState(DWORD sampler, D3DSamplerState state, DWORD value) {
	return HResult(_handle->SetSamplerState(sampler, (D3DSAMPLERSTATETYPE)state, value));
}

HResult Direct3DDevice9::SetVertexShader(Direct3DVertexShader9^ vertexShader) {
	return HResult(_handle->SetVertexShader(vertexShader ? vertexShader->NativeHandle : NULL));
}

HResult Direct3DDevice9::SetPixelShader(Direct3DPixelShader9^ pixelShader) {
	return HResult(_handle->SetPixelShader(pixelShader ? pixelShader->NativeHandle : NULL));
}


HResult Direct3DDevice9::TestCooperativeLevel() {
	return HResult(_handle->TestCooperativeLevel());
}

HResult Direct3DDevice9::Reset(PresentParameters% presentParams) {
	D3DPRESENT_PARAMETERS pp = presentParams.ToD3DPRESENT_PARAMETERS();
	return HResult(_handle->Reset(&pp));
}

HResult Direct3DDevice9::Present(Nullable<System::Drawing::Rectangle> sourceRect, Nullable<System::Drawing::Rectangle> destRect, IntPtr destWindowOverride) {
	RECT source, dest;
	RECT *pSource = NULL, *pDest = NULL;
	if (sourceRect.HasValue) {
		source.left = sourceRect.Value.Left;
		source.top = sourceRect.Value.Top;
		source.right = sourceRect.Value.Right;
		source.bottom = sourceRect.Value.Bottom;
		pSource = &source;
	}

	if (destRect.HasValue) {
		dest.left = destRect.Value.Left;
		dest.top = destRect.Value.Top;
		dest.right = destRect.Value.Right;
		dest.bottom = destRect.Value.Bottom;
		pDest = &dest;
	}

	return HResult(_handle->Present(pSource, pDest, (HWND)destWindowOverride.ToPointer(), NULL));
}


HResult Direct3DDevice9::DrawIndexedPrimitive(D3DPrimitiveType Type, INT BaseVertexIndex, UINT MinIndex, UINT NumVertices, UINT StartIndex, UINT PrimitiveCount) {
	return HResult(_handle->DrawIndexedPrimitive((D3DPRIMITIVETYPE)Type, BaseVertexIndex, MinIndex, NumVertices, StartIndex, PrimitiveCount));
}

struct Vertex {
	float x;
	float y;
	float z;
	unsigned int color;
};

HResult Direct3DDevice9::DrawPrimitive(D3DPrimitiveType Type, UINT StartVertex, UINT PrimitiveCount) {
	return HResult(_handle->DrawPrimitive((D3DPRIMITIVETYPE)Type, StartVertex, PrimitiveCount));
}

void Direct3DDevice9::DebugDraw() {
	Vertex vertices[] = {
		{
			0, 0, 0, 0xFFFFFFFF
		},
		{
			300, 300, 0, 0xFFFF0000
		}
	};
	_handle->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
	_handle->DrawPrimitiveUP(D3DPT_LINELIST, 2, vertices, sizeof(Vertex));
}

HResult Direct3DDevice9::SetShaderConstantF(ShaderType type, UINT startRegister, float* pData, UINT vector4fCount) {
	HRESULT hr = E_NOTIMPL;
	if (type == ShaderType::VertexShader) {
		hr = _handle->SetVertexShaderConstantF(startRegister, pData, vector4fCount);
	} else if (type == ShaderType::PixelShader) {
		hr = _handle->SetPixelShaderConstantF(startRegister, pData, vector4fCount);
	}
	return HResult(hr);
}

HResult Direct3DDevice9::SetShaderConstantI(ShaderType type, UINT startRegister, int* pData, UINT vector4iCount) {
	HRESULT hr = E_NOTIMPL;
	if (type == ShaderType::VertexShader) {
		hr = _handle->SetVertexShaderConstantI(startRegister, pData, vector4iCount);
	} else if (type == ShaderType::PixelShader) {
		hr = _handle->SetPixelShaderConstantI(startRegister, pData, vector4iCount);
	}
	return HResult(hr);
}

HResult Direct3DDevice9::SetShaderConstantB(ShaderType type, UINT startRegister, BOOL* pData, UINT boolCount) {
	HRESULT hr = E_NOTIMPL;
	if (type == ShaderType::VertexShader) {
		hr = _handle->SetVertexShaderConstantB(startRegister, pData, boolCount);
	} else if (type == ShaderType::PixelShader) {
		hr = _handle->SetPixelShaderConstantB(startRegister, pData, boolCount);
	}
	return HResult(hr);
}

void Direct3DDevice9::DebugViewport() {
	D3DVIEWPORT9 viewport;
	viewport.X = 100;
	viewport.Y = 100;
	viewport.Width = 100;
	viewport.Height = 100;
	viewport.MinZ = 0;
	viewport.MaxZ = 1;
	HRESULT hr = _handle->SetViewport(&viewport);
}

HResult Direct3DDevice9::SetCursorProperties(int hotSpotX, int hotSpotY, Direct3DSurface9^ surface) {
	return HResult(_handle->SetCursorProperties(hotSpotX, hotSpotY, surface->NativeHandle));
}

HResult Direct3DDevice9::ShowCursor(bool show) {
	return HResult(_handle->ShowCursor(show));
}

void Direct3DDevice9::SetCursorPosition(int x, int y) {
	return _handle->SetCursorPosition(x, y, 0);
}